var MixlyWebBU = {};

MixlyWebBU.uploading = false;
MixlyWebBU.burning = false;

let espTool;
let isConnected = false;
let stubLoader = null;
let closed = null;

MixlyWebBU.cancel = async function () {
    layer.closeAll('page');
    document.getElementById('serial-device-form').style.display = 'none';
    document.getElementById('webusb-flashing').style.display = 'none';

    if (MixlyWebBU.uploading) {
        MixlyWebBU.uploading = false;
        layer.msg('已取消上传', {
            time: 1000
        });
    } else if (MixlyWebBU.burning) {
        if (port && port.writable) {
            try {
                if (port.writable.locked) {
                    writer.releaseLock();
                }
                //await writer.close();
            } catch (e) {
                console.log(e);
            }
            outputStream = null;
        }
        reader.cancel();
        await closed;
        MixlyWebBU.burning = false;
        MixlyStatusBar.addValue("已取消烧录\n", true);
        MixlyWebBU.toggleUIToolbar(false);
        layer.msg('已取消烧录', {
            time: 1000
        });
    }
}

MixlyWebBU.toggleUIToolbar = function (connected) {
    try {
        if (connected) {
            document.getElementById('operate_connect_btn').textContent = MSG['disconnect'];
            document.getElementById('operate_connect_btn').className = "icon-unlink";
            document.getElementById('connect_btn').textContent = MSG['disconnect'];
            document.getElementById('connect_btn').className = "icon-unlink";
        } else {
            document.getElementById('operate_connect_btn').textContent = MSG['connect'];
            document.getElementById('operate_connect_btn').className = "icon-link";
            document.getElementById('connect_btn').textContent = MSG['connect'];
            document.getElementById('connect_btn').className = "icon-link";
        }
    } catch (e) {
        console.log(e);
    }
}

MixlyWebBU.clickConnect = async function () {
    if (MixlyBU.uploadFileType == "hex") {
        if (!MixlyWebSerial.target) {
            MixlyWebBU.toggleUIToolbar(MixlyWebSerial.selectDevice());
            MixlyStatusBar.setValue("", true);
            $("#serial_content").val("");
        } else {
            try {
                await MixlyWebSerial.target.disconnect();
                MixlyWebBU.toggleUIToolbar(false);
                MixlyWebSerial.target = null;
            } catch (e) {
                console.log(e);
                MixlyWebBU.toggleUIToolbar(true);
            }
        }
    } else {
        MixlyStatusBar.show(1);
        if (espTool.connected()) {
            MixlyWebSerial.dataUpdate && clearInterval(MixlyWebSerial.dataUpdate);
            sleep(100);
            try {
                await disconnect();
            } catch (e) {

            }
            if (MixlyStatusBar.getValue().lastIndexOf("\n") != MixlyStatusBar.getValue().length - 1) {
                MixlyStatusBar.addValue('\n已断开连接\n', true);
            } else {
                MixlyStatusBar.addValue('已断开连接\n', true);
            }
            MixlyWebBU.toggleUIToolbar(false);
            return;
        }
        MixlyStatusBar.setValue("");
        try {
            await espTool.justConnect();
        } catch (e) {
            try {
                await disconnect();
            } catch (e) {

            }
            console.log(e);
            MixlyStatusBar.addValue(e + "\n", true);
            MixlyStatusBar.addValue("已取消连接\n", true);
            MixlyWebBU.toggleUIToolbar(false);
            return;
        }
        MixlyWebSerial.writeCtrlD();
        MixlyWebSerial.dataUpdate = setInterval(MixlyWebSerial.dataRefresh, 100);
        MixlyWebBU.toggleUIToolbar(true);
    }
}

document.addEventListener('DOMContentLoaded', () => {
    espTool = new EspLoader({
        updateProgress: false,
        logMsg: false,
        debugMsg: false,
        debug: false
    })
    window.addEventListener('error', function (event) {
        MixlyStatusBar.addValue("Got an uncaught error: " + event.error + "\n", true)
        console.log("Got an uncaught error: " + event.error)
    });
});

/**
 * @name connect
 * Opens a Web Serial connection to a micro:bit and sets up the input and
 * output stream.
 */
async function connect() {
    MixlyStatusBar.addValue("连接中...\n", true)
    await espTool.connect()
    closed = readLoop();
}

/**
 * @name disconnect
 * Closes the Web Serial connection.
 */
async function disconnect() {
    await espTool.disconnect();
}

function base64ToUint8Array(base64String) {
    const padding = '='.repeat((4 - base64String.length % 4) % 4);
    const base64 = (base64String + padding)
        .replace(/\-/g, '+')
        .replace(/_/g, '/');

    const rawData = window.atob(base64);
    const outputArray = new Uint8Array(rawData.length);

    for (let i = 0; i < rawData.length; ++i) {
        outputArray[i] = rawData.charCodeAt(i);
    }
    return outputArray;
}

MixlyWebBU.burn = async function () {
    async function endBurn(serialClose = false, finish = false) {
        if (serialClose) {
            reader.cancel();
            await closed;
        }
        if (finish) {
            errorMsg("To run the new firmware, please reset your device.")
            MixlyStatusBar.addValue("要运行新固件，请重置您的设备。\n", true);
        }
        MixlyWebBU.toggleUIToolbar(false);
        layer.closeAll('page');
        document.getElementById('webusb-flashing').style.display = 'none';
        MixlyWebBU.burning = false;
        MixlyWebBU.uploading = false;
    }
    MixlyWebBU.burning = true;
    MixlyWebBU.uploading = false;
    MixlyStatusBar.setValue("");
    MixlyStatusBar.show(1);

    try {
        await clickConnect();
    } catch (e) {
        console.log(e);
        MixlyStatusBar.addValue("已取消烧录\n", true);
        MixlyWebBU.burning = false;
        MixlyWebBU.uploading = false;
        return;
    }

    if (espTool.connected()) {
        MixlyWebBU.toggleUIToolbar(true);
        layui.use('layer', function () {
            var layer = layui.layer;
            layer.open({
                type: 1,
                title: '烧录中...',
                content: $('#webusb-flashing'),
                closeBtn: 0,
                end: function () {
                    document.getElementById('webusb-flashing').style.display = 'none';
                }
            });
        });
        MixlyStatusBar.addValue("固件读取中... ", true);
        if (!(MixlyUrl.BOARD_CONFIG?.web?.burn?.binFile
            && MixlyUrl.BOARD_CONFIG.web.burn.binFile[0]
            && MixlyUrl.BOARD_CONFIG.web.burn.binFile[0].path
            && MixlyUrl.BOARD_CONFIG.web.burn.binFile[0].offset)) {
            MixlyStatusBar.addValue("Failed!\n配置文件读取出错！\n", true);
            await endBurn(true, false);
            return;
        }
        fetch(MixlyUrl.BOARD_CONFIG.web.burn.binFile[0].path)
        .then((response) => {
            return response.blob();
        })
        .then(async (data) => {
            MixlyStatusBar.addValue("Done!\n即将开始烧录...\n", true);
            if (espTool.connected()) {
                firmware = data;
                const readUploadedFileAsArrayBuffer = (inputFile) => {
                    const reader = new FileReader();

                    return new Promise((resolve, reject) => {
                        reader.onerror = () => {
                            reader.abort();
                            reject(new DOMException("Problem parsing input file."));
                        };

                        reader.onload = () => {
                            resolve(reader.result);
                        };
                        reader.readAsArrayBuffer(inputFile);
                    });
                };
                let binBuf = await readUploadedFileAsArrayBuffer(data);
                let binOffset = MixlyUrl.BOARD_CONFIG.web.burn.binFile[0].offset;
                if (await clickSync()) {
                    if (MixlyUrl.BOARD_CONFIG?.web?.burn?.erase) {
                        await clickErase();
                    }

                    await clickProgram(binOffset, binBuf);
                }
                await endBurn(true, true);
            } else {
                MixlyStatusBar.addValue("设备已断开连接！\n", true);
                await endBurn(false, true);
            }
        }).catch(async function(e) {
            MixlyStatusBar.addValue("Failed!\n无法获取文件，请检查网络！\n", true);
            MixlyStatusBar.addValue("\n" + e + "\n", true);
            await endBurn(true, false);
        });
    }
}

async function clickDisconnect() {
    if (espTool.connected()) {
        await disconnect();
    }
}

/**
 * @name clickConnect
 * Click handler for the connect/disconnect button.
 */
async function clickConnect() {
    if (!espTool.connected()) {
        await connect();
    } else {
        MixlyWebSerial.dataUpdate && clearInterval(MixlyWebSerial.dataUpdate);
        sleep(100);
        await espTool.reset();
        closed = readLoop();
    }
    MixlyWebBU.toggleUIToolbar(true);
}

async function clickSync() {
    try {
        if (await espTool.sync()) {
            let baud = 115200;
            MixlyStatusBar.addValue("正在连接 " + await espTool.chipName() + "\n", true);
            MixlyStatusBar.addValue("MAC地址：" + formatMacAddr(espTool.macAddr()) + "\n", true);
            stubLoader = await espTool.runStub();
            return 1;
        }
    } catch (e) {
        MixlyStatusBar.addValue(e + "\n", true);
        MixlyWebBU.toggleUIToolbar(false);
        await disconnect();
    }
    return 0;
}

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

/**
 * @name clickErase
 * Click handler for the erase button.
 */
async function clickErase() {
    try {
        MixlyStatusBar.addValue("正在擦除闪存，请稍等..." + "\n", true);
        let stamp = Date.now();
        await stubLoader.eraseFlash();
        MixlyStatusBar.addValue("擦除完成. 擦除耗时" + (Date.now() - stamp) + "ms" + "\n", true);
        await sleep(100);
    } catch (e) {
        MixlyStatusBar.addValue(e, true);
    } finally {

    }
}

/**
 * @name clickProgram
 * Click handler for the program button.
 */
async function clickProgram(binFileOffset, binFile = null) {
    MixlyStatusBar.addValue("正在烧录固件\n", true);
    try {
        let binOffset = parseInt(binFileOffset, 16);
        await stubLoader.flashData(binFile, binOffset);
        await sleep(100);
    } catch (e) {
        errorMsg(e);
        MixlyStatusBar.addValue(e + "\n", true);
    }
}

function errorMsg(text) {
    console.log(text);
}

/**
 * @name readLoop
 * Reads data from the input stream and places it in the inputBuffer
 */
async function readLoop() {
    let keepReading = true;
    MixlyStatusBar.addValue("循环读取开始" + "\n", true);
    while (port && port.readable && keepReading) {
        if (port.readable.locked) {
            await reader.cancel();
            try {
                reader.releaseLock();
            } catch (e) {
                console.log(e);
            }
        }
        reader = port.readable.getReader();
        try {
            while (true) {
                const { value, done } = await reader.read();
                if (done || !MixlyWebBU.burning) {
                    reader.releaseLock();
                    inputStream = null;
                    try {
                        if (port.writable.locked) {
                            writer.releaseLock();
                        }
                    } catch (e) {
                        console.log(e);
                    }
                    outputStream = null;
                    console.log("done!");
                    keepReading = false;
                    break;
                }
                inputBuffer = inputBuffer.concat(Array.from(value));
            }
        } catch (error) {
            // Handle |error|...
        } finally {
            reader.releaseLock();
        }
    }
    try {
        await port.close();
    } catch (e) {
        console.log(e);
    }
    port = null;
}

async function closeReader() {
    keepReading = false;
    reader.cancel();
    await closed;
}

function formatMacAddr(macAddr) {
    return macAddr.map(value => value.toString(16).toUpperCase().padStart(2, "0")).join(":");
}

MixlyWebBU.upload = async function () {
    if (MixlyBU.uploadFileType == "hex") {
        MixlyWebSerial.update();
    } else {
        MixlyWebBU.uploading = true;
        MixlyWebBU.burning = false;
        if (MixlyBU.uploadType == "volumeLabel") {
            mixlyjs.savePyFileAs();
        } else {
            MixlyStatusBar.show(1);
            if (!espTool.connected()) {
                try {
                    await espTool.justConnect(async function () {
                        MixlyWebBU.toggleUIToolbar(true);
                        await MixlyWebBU.uploadWithAmpy();
                    });
                } catch (e) {
                    console.log(e);
                    MixlyStatusBar.addValue(e + "\n", true);
                    MixlyStatusBar.addValue("已取消连接\n", true);
                    MixlyWebBU.toggleUIToolbar(false);
                    return;
                }
            } else {
                await MixlyWebBU.uploadWithAmpy();
            }
        }
        MixlyWebBU.uploading = false;
        MixlyWebBU.burning = false;
        MixlyWebSerial.dataUpdate = setInterval(MixlyWebSerial.dataRefresh, 100);
    }
}

MixlyWebBU.uploadWithAmpy = async function () {
    // 判断字符是否为汉字，
    function isChinese(s){
        return /[\u4e00-\u9fa5]/.test(s);
    }

    // 中文unicode编码
    function ch2Unicode(str) {
        if(!str){
            return;
        }
        var unicode = '';
        for (var i = 0; i <  str.length; i++) {
            var temp = str.charAt(i);
            if(isChinese(temp)){
                unicode += '\\u' +  temp.charCodeAt(0).toString(16);
            }
            else{
                unicode += temp;
            }
        }
        return unicode;
    }

    try {
        layui.use('layer', function () {
            var layer = layui.layer;
            layer.open({
                type: 1,
                title: '上传中...',
                content: $('#webusb-flashing'),
                closeBtn: 0,
                end: function () {
                    layer.closeAll('page');
                    document.getElementById('webusb-flashing').style.display = 'none';
                }
            });
        });
        MixlyStatusBar.setValue("", true);
        await espTool.setDTR(false);
        await espTool.setRTS(false);
        await sleep(100);
        await espTool.setDTR(true);
        await espTool.setRTS(true);
        await sleep(1000);
        await MixlyWebSerial.writeCtrlC();
        await sleep(100);
        await espTool.write("file = open('main.py', 'w')\r\n");
        let writeData = MixlyFile.getPy().replace(/\n/g, "\\n");
        writeData = ch2Unicode(writeData);
        await espTool.write("file.write('''" + writeData + "''')\r\n");
        await espTool.write("file.close()\r\n");
        await sleep(100);
        await MixlyWebSerial.writeCtrlD();
        await sleep(500);
        await espTool.setDTR(false);
        await espTool.setRTS(false);
        MixlyStatusBar.setValue("", true);
        layer.closeAll('page');
        document.getElementById('webusb-flashing').style.display = 'none';
        layer.msg('上传成功！', {
            time: 1000
        });
    } catch (e) {
        MixlyStatusBar.addValue(e + "\n", true);
        layer.closeAll('page');
        document.getElementById('webusb-flashing').style.display = 'none';
        layer.msg('上传失败', {
            time: 1000
        });
    }
}